package cn.z2huo.demo.spring.webmvc.controller.download;

import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import org.springframework.web.util.UriUtils;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
 * <p>文件下载
 *
 * @author z2huo
 */
@Slf4j
@RestController
public class DownloadFileController {

    /**
     * <p>一次性下载多个文件，通过 ResponseEntity
     * <p>使用小文件试一下先
     */
    public ResponseEntity<byte[]> downloadMultiFile() {
        log.info("downloadLargeFile start");
        try {
            ClassPathResource classPathResource = new ClassPathResource("csv/problem/NO_0003/test_data_10000.csv");
            ClassPathResource classPathResource2 = new ClassPathResource("csv/problem/NO_0003/test_data_10000_2.csv");

            String fileName = "多个文件-1.csv";

            ContentDisposition contentDisposition = ContentDisposition.attachment()
                    .filename(fileName, StandardCharsets.UTF_8)
                    .build();

            HttpHeaders headers = new HttpHeaders();
            headers.set(HttpHeaders.CONTENT_ENCODING, "UTF-8");
            headers.setContentDisposition(contentDisposition);

            ResponseEntity<byte[]> responseEntity = ResponseEntity.ok()
                    .headers(headers)
                    .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
                    .contentLength(classPathResource.contentLength())
                    .body(classPathResource.getContentAsByteArray());
            log.info("downloadLargeFile end");
            return responseEntity;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * <p>大文件下载，使用 ResponseEntity，body 中放的是文件的字节数组
     * <p>使用 byte[] 返回文件，会将文件的字节数组一次性都读取到内存中，占用内存较高
     * @return  返回字节数组
     */
    @GetMapping("/downloadLargeFile")
    public ResponseEntity<byte[]> downloadLargeFile() {
        log.info("downloadLargeFile start");
        try {
//            ClassPathResource classPathResource = new ClassPathResource("csv/problem/NO_0003/test_data_10000.csv");
//            ClassPathResource classPathResource = new ClassPathResource("csv/problem/NO_0003/test_data_5000000.csv");
            ClassPathResource classPathResource = new ClassPathResource("csv/problem/NO_0003/test_data_20000000.csv");

            String fileName = "大csv文件.csv";

            ContentDisposition contentDisposition = ContentDisposition.attachment()
                    .filename(fileName, StandardCharsets.UTF_8)
                    .build();

            HttpHeaders headers = new HttpHeaders();
            headers.set(HttpHeaders.CONTENT_ENCODING, "UTF-8");
            headers.setContentDisposition(contentDisposition);

//            long contentLength = classPathResource.contentLength();

            ResponseEntity<byte[]> responseEntity = ResponseEntity.ok()
                    .headers(headers)
                    .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
//                    .contentLength(contentLength)
                    .body(classPathResource.getContentAsByteArray());
            log.info("downloadLargeFile end");
            return responseEntity;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * <p>大文件下载，使用 ResponseEntity，body 中存放的使文件的 Resource
     * <p>使用 Resource 返回文件，Resource 中是文件的 InputStream，所以最终还是文件的输入流与响应输出流之间的数据传输
     * @return  返回字节数组
     */
    @GetMapping("/downloadLargeFileResource")
    public ResponseEntity<Resource> downloadLargeFileResource() {
        log.info("downloadLargeFileResource start");
        try {
//            ClassPathResource classPathResource = new ClassPathResource("csv/problem/NO_0003/test_data_10000.csv");
//            ClassPathResource classPathResource = new ClassPathResource("csv/problem/NO_0003/test_data_5000000.csv");
            ClassPathResource classPathResource = new ClassPathResource("csv/problem/NO_0003/test_data_20000000.csv");

            InputStreamResource resource = new InputStreamResource(classPathResource);

            String fileName = "大csv文件.csv";

            ContentDisposition contentDisposition = ContentDisposition.attachment()
                    .filename(fileName, StandardCharsets.UTF_8)
                    .build();

            HttpHeaders headers = new HttpHeaders();
            headers.set(HttpHeaders.CONTENT_ENCODING, "UTF-8");
            headers.setContentDisposition(contentDisposition);

//            long contentLength = classPathResource.contentLength();

            ResponseEntity<Resource> responseEntity = ResponseEntity.ok()
                    .headers(headers)
                    .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
//                    .contentLength(contentLength)
                    .body(resource);
//                    .responseEntity(classPathResource);
            log.info("downloadLargeFile end");
            return responseEntity;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * <p>大文件下载，使用输出流
     */
    @GetMapping("/downloadLargeFileOutputStream")
    public void downloadLargeFileOutputStream(HttpServletResponse response) {
        log.info("downloadLargeFileOutputStream start");

//        ClassPathResource resource = new ClassPathResource("csv/problem/NO_0003/test_data_5000000.csv");
        ClassPathResource resource = new ClassPathResource("csv/problem/NO_0003/test_data_20000000.csv");

//        String fileName = "大csv文件.csv";
        String fileName = URLEncoder.encode("大csv文件.csv", StandardCharsets.UTF_8).replace("+", "%20");

        try {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            long contentLength = resource.contentLength();
//            response.setContentLength((int) contentLength);
            response.setCharacterEncoding("UTF-8");
            response.setHeader(HttpHeaders.CONTENT_ENCODING, "UTF-8");
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename*=utf-8''" + fileName);
            // 与 Content-Length 冲突：如果使用了 Transfer-Encoding: chunked，则不能设置 Content-Length 头部。
//            response.setHeader(HttpHeaders.TRANSFER_ENCODING, "chunked");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        try (InputStream inputStream = resource.getInputStream()) {
            ServletOutputStream outputStream = response.getOutputStream();
            byte[] bufferByteArray = new byte[1024 * 1024];
            int bytesReadCount;
//            int loopCount = 1;
            while ((bytesReadCount = inputStream.read(bufferByteArray)) != -1) {
//                ThreadUtil.sleep(10, TimeUnit.MILLISECONDS);
//                log.info("loop count {}, read byte count is {}", loopCount++, bytesReadCount);
                outputStream.write(bufferByteArray, 0, bytesReadCount);
                outputStream.flush();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        log.info("downloadLargeFileOutputStream end");
    }

    @GetMapping("/downloadLargeFileStreamingResponseBody")
    public ResponseEntity<StreamingResponseBody> downloadLargeFileStreamingResponseBody() {
        log.info("downloadLargeFileStreamingResponseBody start");

//        ClassPathResource resource = new ClassPathResource("csv/problem/NO_0003/test_data_5000000.csv");
        ClassPathResource resource = new ClassPathResource("csv/problem/NO_0003/test_data_20000000.csv");

        String fileName = "大csv文件.csv";
        ContentDisposition contentDisposition = ContentDisposition.attachment()
//                .filename(fileName, StandardCharsets.UTF_8)
                .filename(UriUtils.encode(fileName, StandardCharsets.UTF_8))
                .build();

        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.CONTENT_ENCODING, "UTF-8");
        headers.setContentDisposition(contentDisposition);

        StreamingResponseBody streamingResponseBody = outputStream -> {
            log.info("write StreamingResponseBody start");
            try (InputStream input = resource.getInputStream()) {
                byte[] bufferByteArray = new byte[1024 * 1024];
                int bytesReadCount;
                while ((bytesReadCount = input.read(bufferByteArray)) != -1) {
                    outputStream.write(bufferByteArray, 0, bytesReadCount);
                    outputStream.flush();
                }
            }
            log.info("write StreamingResponseBody end");
        };

        ResponseEntity<StreamingResponseBody> responseEntity = ResponseEntity.ok()
                .headers(headers)
                .body(streamingResponseBody);

        log.info("downloadLargeFileStreamingResponseBody end");
        return responseEntity;
    }

    /**
     * <p>将多个文件打包并下载压缩包
     *
     */
    @PostMapping("/")
    public void downloadZipFile() {

    }

}
